<script>
import {
    defineComponent,
    ref,
    reactive,
    onActivated,
    toRef,
    nextTick,
    onMounted,
    onUnmounted,
    watch,
} from 'vue';
import { simpleRoll } from '@/common/rollTolls';
import PerfectScrollbar from 'perfect-scrollbar';
import 'perfect-scrollbar/css/perfect-scrollbar.css';
import { ResizeObserver } from 'resize-observer';

export default defineComponent({
    components: {},
    props: {
        /** 是否含有回到顶部按钮 */
        hasUpBt: {
            type: Boolean,
            default: true,
        },
        /** 是否含有loading元素 */
        hasLoadingEl: {
            type: Boolean,
            default: true,
        },
        /** 加载状态 */
        loading: {
            type: Boolean,
            default: false,
        },
    },
    emits: ['onScroll'],
    setup(props, { emit }) {
        const DefinScrollbarRef = ref(null); // 组件实例
        const DefinScrollbarOptionContainerRef = ref(null); // 组件实例
        const DefinScrollbarUpBtRef = ref(null); // 组件实例
        const DefinScrollbarLoadingRef = ref(null); // 组件实例
        const dataContainer = reactive({
            hasUpBt: toRef(props, 'hasUpBt'),
            hasLoadingEl: toRef(props, 'hasLoadingEl'),
            loading: toRef(props, 'loading'),
        });
        const otherDataContainer = {
            top: 0,
            left: 0,
            showBt: false,
            definScrollbarEl: undefined,
            definScrollbarOptionContainerEl: undefined,
            timer: undefined,
            ro: undefined,
            ps: undefined,
        };
        /** 页面加载好了找出滚动容器 */
        onMounted(() => {
            otherDataContainer.definScrollbarEl = DefinScrollbarRef.value;
            otherDataContainer.definScrollbarOptionContainerEl =
                DefinScrollbarOptionContainerRef.value;
            addEvent();
            setupScroll();
            setupLoadingElShow(dataContainer.loading);
            const ro = new ResizeObserver(() => {
                setupStyle();
                updatePs();
            });
            ro.observe(otherDataContainer.definScrollbarEl);
        });
        /** 重新加载时候赋予旧值 */
        onActivated(() => {
            nextTick(() => {
                restoreScroll();
            });
        });
        otherDataContainer.timer = setInterval(() => {
            setupStyle();
            updatePs();
        }, 500);
        onUnmounted(() => {
            clearInterval(otherDataContainer.timer);
        });
        watch([toRef(props, 'loading')], (value) => {
            setupLoadingElShow(value);
        });
        /** 设置滚动条 */
        function setupScroll() {
            const el = otherDataContainer.definScrollbarEl;
            if (!el) return;
            otherDataContainer.ps = new PerfectScrollbar(el, {
                handlers: ['click-rail', 'drag-thumb', 'keyboard', 'touch'],
            });
        }
        /** 添加滚动事件 */
        function addEvent() {
            const el = otherDataContainer.definScrollbarEl;
            if (!el) return;
            el.addEventListener('scroll', handleScroll);
        }
        /** 更新ps */
        function updatePs() {
            if (!otherDataContainer.ps) return;
            otherDataContainer.ps.update();
        }
        /** 还原滚动条位置 */
        function restoreScroll() {
            const el = otherDataContainer.definScrollbarEl;
            if (!el) return;
            el.scrollTop = otherDataContainer.top || 0;
            el.scrollLeft = otherDataContainer.left || 0;
        }
        /** 处理样式 */
        function setupStyle() {
            let el = otherDataContainer.definScrollbarEl;
            let el_1 = otherDataContainer.definScrollbarOptionContainerEl;
            if (!el || !el_1) return;
            const rect = el.getBoundingClientRect();
            // 计算子元素的宽高度
            let width = 0;
            let height = 0;
            el.children.forEach((el__) => {
                if (!el__.classList.contains('scroll-container')) return;
                const rect = el__.getBoundingClientRect();
                width = width + rect.width;
                height = height + rect.height;
            });
            const overflowWidth = Math.max(0, width - rect.width);
            const overflowHeight = Math.max(0, height - rect.height);
            // 设置配置容器的位置
            el_1.style.top = Math.min(el.scrollTop, overflowHeight) + 'px';
            el_1.style.left = Math.min(el.scrollLeft, overflowWidth) + 'px';
            el_1.style.width = rect.width + 'px';
            el_1.style.height = rect.height + 'px';
        }
        /** 记录滚动位置 */
        function recordScroll() {
            let el = otherDataContainer.definScrollbarEl;
            if (!el) return;
            if (el.scrollTop > 40) {
                setupBtElShow(true);
            } else {
                setupBtElShow(false);
            }
            otherDataContainer.top = el.scrollTop || 0;
            otherDataContainer.left = el.scrollLeft || 0;
        }
        /** 滚动事件 */
        function handleScroll(e) {
            recordScroll();
            // 设置样式
            setupStyle();
            /** 向外部抛出事件 */
            emit('onScroll', e);
        }
        /** 回到顶部事件 */
        function handleUp() {
            const el = otherDataContainer.definScrollbarEl;
            if (!el) return;
            simpleRoll({
                top: 0,
                time: 150,
                target: el,
            });
        }
        /** 滚动到某一位置params {left:0,top:0} */
        function handleTo(params) {
            const el = otherDataContainer.definScrollbarEl;
            if (!el) return;
            simpleRoll(
                Object.assign(
                    {
                        target: el,
                    },
                    params,
                ),
            );
        }
        /** 设置按钮的显示 */
        function setupBtElShow(show) {
            const el = DefinScrollbarUpBtRef.value;
            if (!el) return;
            if (show) {
                el.classList.add('show');
            } else {
                el.classList.remove('show');
            }
        }
        /** 设置loading的显示 */
        function setupLoadingElShow(show) {
            const el = DefinScrollbarLoadingRef.value;
            if (!el) return;
            if (show) {
                el.classList.add('show');
            } else {
                el.classList.remove('show');
            }
        }
        return {
            dataContainer,
            DefinScrollbarRef,
            DefinScrollbarOptionContainerRef,
            DefinScrollbarUpBtRef,
            DefinScrollbarLoadingRef,
            handleScroll,
            handleUp,
            handleTo,
        };
    },
});
</script>

<template>
    <div ref="DefinScrollbarRef" class="defin-scrollbar">
        <slot></slot>
        <div class="option-container" ref="DefinScrollbarOptionContainerRef">
            <div
                class="scrollbar-up-bt"
                ref="DefinScrollbarUpBtRef"
                v-if="dataContainer.hasUpBt"
                @click="handleUp"
            >
                UP
            </div>
            <div
                ref="DefinScrollbarLoadingRef"
                class="loading-container"
                v-if="dataContainer.hasLoadingEl"
            >
                <div class="title">LOADING...</div>
                <div class="loading"></div>
            </div>
        </div>
    </div>
</template>

<style lang="scss" scoped>
.defin-scrollbar {
    position: relative;
    width: 100%;
    height: 100%;
    overflow: auto !important;
    > .option-container {
        height: 100%;
        width: 100%;
        left: 0;
        top: 0;
        position: absolute;
        z-index: 999;
        pointer-events: none;
        > .scrollbar-up-bt {
            pointer-events: initial;
            position: absolute;
            z-index: 999;
            bottom: 3vw;
            right: 3vw;
            width: 40px;
            height: 40px;
            background-color: #282828;
            border-radius: 5px;
            display: flex;
            justify-content: center;
            align-items: center;
            line-height: 1;
            opacity: 0;
            pointer-events: none;
            transition: all 0.2s;
            cursor: pointer;
            color: rgb(25, 137, 250);
            font-size: 20px;
            font-weight: bold;
            font-family: serif;
            box-shadow: 0px 0px 6px rgba(0, 0, 0, 0.667);
            &.show {
                opacity: 1;
                pointer-events: initial;
            }
            &:hover {
                transform: scale(1.1);
            }
        }
        > .loading-container {
            pointer-events: initial;
            position: absolute;
            top: 0;
            left: 0;
            height: 100%;
            width: 100%;
            z-index: 999;
            display: flex;
            justify-content: center;
            align-items: center;
            overflow: hidden;
            color: #dde1e4;
            pointer-events: none;
            transition: all 0.3s;
            opacity: 0;
            &.show {
                opacity: 0.2;
            }
        }
        > .loading-container .title {
            position: absolute;
            font-size: 20px;
            font-family: Source Han Serif CN;
            font-weight: bold;
            letter-spacing: 5px;
            transform: translateY(100px);
            font-style: italic;
        }
        > .loading-container .loading {
            position: absolute;
        }
        > .loading-container .loading {
            width: 8px;
            height: 100px;
            border-radius: 4px;
            display: inline-block;
            position: relative;
            background: currentColor;
            color: #dde1e4;
            animation: loading-animation 0.3s 0.3s linear infinite alternate;
        }
        > .loading-container .loading::after,
        > .loading-container .loading::before {
            content: '';
            width: 8px;
            height: 100px;
            border-radius: 4px;
            background: currentColor;
            position: absolute;
            top: 50%;
            transform: translateY(-50%);
            left: 80px;
            animation: loading-animation 0.3s 0.45s linear infinite alternate;
        }
        > .loading-container .loading::before {
            left: -80px;
            animation-delay: 0s;
        }
        @keyframes loading-animation {
            0% {
                height: 100px;
            }
            100% {
                height: 4.8px;
            }
        }
    }
}
</style>
